﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Threading;
using agsXMPP;
using agsXMPP.protocol.client;
using XcoAppSpaces.Contracts.Transport;
using XcoAppSpaces.Contracts.Logging;
using XcoAppSpaces.Contracts.Exceptions;

namespace XcoAppSpaces.Transport.Jabber
{
    public class XcoJabberTransportService : IXcoTransportService
    {
		/// <summary>
		/// Event that is raised when a message is received.
		/// </summary>
		public event MessageReceivedDelegate OnMessageReceived;

        public const int DEFAULT_PRIORITY = 64;
        private const int CONNECTION_OPEN_TIMEOUT_MSEC = 10000;
        private const int CONNECTION_CLOSE_TIMEOUT_MSEC = 5000;

        private readonly Jid jid;
        private readonly string password;
        private readonly string hostname;
        private int priority;

        public Jid JID { get { return jid; } }
        public string Password { get { return password; } }
        public string Hostname { get { return hostname; } }
        private TransferHelper transferHelper ;

        private XmppClientConnection connection;

        private readonly IXcoLogger logger;


        public XcoJabberTransportService(string jabberId, string password)
            : this(jabberId, password, "", DEFAULT_PRIORITY)
        {}

        public XcoJabberTransportService(string jabberId, string password, string hostname)
            : this(jabberId, password, hostname, DEFAULT_PRIORITY)
        { }

        public XcoJabberTransportService(string jabberId, string password, int priority)
            : this(jabberId, password, "", priority)
        { }

        public XcoJabberTransportService(string jabberId, string password, string hostname, int priority)
        {
            this.jid = new Jid(jabberId);
            this.password = password;
            this.hostname = hostname;
            this.priority = priority;
			this.logger = XcoLoggerFactory.CreateInstance(this.GetType());
            transferHelper = new TransferHelper();
        }


        public int Priority
        {
            get { return this.priority; }
            set
            {
                this.priority = value;
                if (this.connection != null)
                {
                    this.connection.Priority = this.priority;
                    this.connection.SendMyPresence();
                }
            }
        }


        #region IXcoTransportService Members

        public void Stop()
        {
            this.logger.Info("Stopping communication service");
           
            Close();
        }

        private void Close()
        {
            if (this.connection != null)
            {
                this.logger.Info("Closing server connection");
                AutoResetEvent closedSignal = new AutoResetEvent(false);
                this.connection.OnClose += (o =>
                                                {
                                                    this.logger.Info(string.Format("Connection closed for {0}", this.jid));
                                                    closedSignal.Set();
                                                });
                this.connection.Close();
                //TODO: catch timeout and throw exception?
                closedSignal.WaitOne(CONNECTION_CLOSE_TIMEOUT_MSEC);
                this.connection = null;
            }
        }

        public string LocalAddress
        {
            get { return this.jid.ToString(); }
        }
                
        public void Send(XcoMessage msg, string remoteAddress)
        {
			try
			{
				if (this.connection == null)
				{
					string errorMsg = string.Format("Cannot send message! No connection established for jid {0}.", this.jid);
					this.logger.Error(errorMsg);
					msg.RaiseTransferFailedEvent(new XcoCommunicationException(errorMsg));
				}

				string serverJid = remoteAddress.Replace("xco.jabber://", "");
				this.logger.Info(string.Format("Sending message to {0}", serverJid));

				byte[] msgBuffer = transferHelper.Convert(msg, this.LocalAddress);
				string msgBase64 = Convert.ToBase64String(msgBuffer);
				Message msgJabber = new Message(new Jid(serverJid), msgBase64);
				this.connection.Send(msgJabber);
				msg.RaiseTransferSuccessfulEvent();
			}
			catch (Exception ex)
			{
				msg.RaiseTransferFailedEvent(new XcoCommunicationException("Error sending message to " + remoteAddress + ": " + ex.Message, ex));
			}
        }


        private void OnMessageReceivedHandler(object o, Message jabberMsg)
        {
            this.logger.Info(string.Format("Received message from {0}", jabberMsg.From));

            char[] payloadChars = jabberMsg.Body.ToCharArray();
            byte[] msgBuffer = Convert.FromBase64CharArray(payloadChars, 0, payloadChars.Length);
        	string remoteAddress;
            XcoMessage xcoMsg = transferHelper.Convert(msgBuffer, out remoteAddress);
            if (xcoMsg != null)
            {
                remoteAddress = jabberMsg.From.ToString();
                OnMessageReceived(xcoMsg, remoteAddress, this);
            }
        }

        #endregion


        #region IDisposable Members

        public void Dispose()
        {
            this.logger.Info("Disposing communication service");
            Stop();
        }

        #endregion

		#region IXcoStatefulService Members

		public void Start()
		{
			if (this.connection == null)
			{
				this.logger.Info("Starting communication service");

				this.connection = new XmppClientConnection(this.jid.Server);
				AutoResetEvent loggedInSignal = new AutoResetEvent(false);

				this.connection.OnMessage += OnMessageReceivedHandler;

				this.connection.OnLogin += (o =>
				{
					this.logger.Info(string.Format("Successfully connected as {0}", this.jid));
					loggedInSignal.Set();
				});

				if (string.IsNullOrEmpty(this.hostname))
					this.connection.AutoResolveConnectServer = true;
				else
					this.connection.ConnectServer = this.hostname;

				this.connection.Open(this.jid.User, this.password, this.jid.Resource, this.priority);
				if (!loggedInSignal.WaitOne(CONNECTION_OPEN_TIMEOUT_MSEC))
				{
					this.logger.Error(string.Format("Timeout on connect for {0}", this.jid));

					Stop();
					throw new TimeoutException(string.Format("Unable to connect to Jabber server within timeout period using JID {0}!", this.jid));
				}
			}
		}

		public int StartPriority
		{
			get { return 5; }
		}

		#endregion

		#region IXcoService Members

		public void Initialize(XcoAppSpaces.Contracts.Service.IXcoServiceRegistry serviceRegistry)
		{
		}

		#endregion
	}
}
